JUnit5 & MockMvc & Mockito

您所在的位置:网站首页 源码解析 junit5 JUnit5 & MockMvc & Mockito

JUnit5 & MockMvc & Mockito

2023-06-05 17:11| 来源: 网络整理| 查看: 265

基于 spring-boot-start 2.2.1.RELEASE 写 Controller 的测试类时,出了好多异常的状况。特将正常运行的代码记录下来,以防遗忘。

依赖版本

使用 Maven 管理的依赖。

JDK: 1.8 parent:spring-boot-starter-parent 2.2.1.RELEASE dependencies: spring-boot-starter-web 2.2.1.RELEASE spring-boot-starter-test 2.2.1.RELEASE 测试类注意点

之所以踩了这么多坑,总结下来主要是因为网上的示例大都是基于不同的版本,从而导致写法也各不相同。将各种写法混在一起,又出现了各种奇怪的问题。 另外,我在公司的项目(依赖的包比较多)和我单独写的测试应用中(Spring 包的版本和公司项目是一致的),运行的结果也不一致。 最终的正确写法主要还是参考的官方文档,里面的说明比较详细和准确。

下面是总结的需要注意的地方:

2.2.1.RELEASE 版本中对应 JUnit 的版本是 5.5.2,测试类要使用 @SpringBootTest 注解;

很多文档中使用了 @RunWith(SpringRunner.class) 注解,SpringRunner 是 JUnit4 中声明测试类用的。 也不要添加 @ExtendWith({SpringExtension.class}),@SpringBootTest 中已经包含了这个注解。

@SpringBootTest class UserControllerTest { // ... }

如果测试 Controller 接口需要添加 @AutoConfigureMockMvc 注解;

使用 @SpringBootTest 注解,默认是不启动 Server 的。 大多数示例中都是在 @Before 方法中通过 MockMvcBuilders 来创建 MockMvc 实例。 另外在 JUnit5 中已经取消了 @Before 注解,改为使用 @BeforeEach 注解。

@AutoConfigureMockMvc @SpringBootTest class UserControllerTest { @Autowired private MockMvc mockMvc; @Test void login() throws Exception { ResultActions resultActions = this.mockMvc.perform(post("/user/login")); // 解决中文乱码问题 resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8"); resultActions .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string(containsString("OpenId B"))) .andDo(MockMvcResultHandlers.print()); } }

如果需要 Mock 注入的服务等,需要添加 @ExtendWith(MockitoExtension.class) 注解,并且需要添加 @MockBean 在对应的字段(测试类中的字段)上;

很多文档都是使用的 @Mock 和 @IndectMocks 注解配合来实现 Mock 第三方服务的。

@AutoConfigureMockMvc @SpringBootTest @ExtendWith(MockitoExtension.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private WxService wxService; @Test void login() throws Exception { WxLoginResponse wxLoginResponse = new WxLoginResponse(); wxLoginResponse.setOpenId("假用户 OpenId B"); Mockito.when(wxService.login(any(), any(), any(), any())) .thenReturn(wxLoginResponse); // ... } }

测试中曾经出现过的问题:

由于 Controller 中使用了 @RequiredArgsConstructor 注解,导致 Mock 始终不起作用; 这个最终实现了使用 @RequiredArgsConstructor 注解时仍然可以正常 Mock 。不过当时确实通过改为使用 @Autowired 注解实现了 Mock ,不过随之而来的是 Mock 的接口返回值始终是 null 的问题。 Mock 的服务返回的结果始终是 null; 这个在正式项目和测试项目中运行的结果经常不一致;测试项目中大部分写法都是可以正常返回的,但是正式项目中,不知道什么原因,一致返回的是 null 。 只有通过 this.mockMvc = MockMvcBuilders.standaloneSetup(userController).build(); 才能注入 Mock 的服务,但最后发现在 JUnit5 是不需要手动创建 mockMvc 实例的,可以简单的通过添加 @Autowired 注解来注入。 Controller 中除了 Mock 的服务,其它需要注入的组件全都是 null; 查到的很多文档中都是通过在被 Mock 的服务字段上添加 @Mock 注解,在使用 Mock 服务的组件上添加 @InjectMocks 注解。 但是在 JUnit5 中只需要在被 Mock 的服务上添加 @MockBean 注解,需要在测试类中定义使用 Mock 服务的组件字段。 接口调用时,Filter 没有执行; 之前是通过在 mockMvc 的 builder 处理中添加 .addFilter(signAuthFilter) 来实现的,但是之后使用 @AutoConfigureMockMvc 注解来注入 mockMvc 实例后,就不需要手动添加 filter 了。 附1. 完整示例代码 1. pom.xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE me.liujiajia junit5-sample 0.0.1-SNAPSHOT junit5-sample Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.projectlombok lombok org.springframework.boot spring-boot-starter-test test org.junit.vintage junit-vintage-engine org.springframework.boot spring-boot-maven-plugin 2. Junit5SampleApplication.java package me.liujiajia.junit5sample; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Junit5SampleApplication { public static void main(String[] args) { SpringApplication.run(Junit5SampleApplication.class, args); } } 3. UserController.java & WxLoginResponse.java package me.liujiajia.junit5sample.controller; import lombok.extern.slf4j.Slf4j; import me.liujiajia.junit5sample.entity.WxLoginResponse; import me.liujiajia.junit5sample.service.AnotherService; import me.liujiajia.junit5sample.service.WxService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * Created by 佳佳 on 2021/4/26. */ @RestController @RequestMapping("/user") @Slf4j public class UserController { @Autowired private WxService wxService; @Autowired private AnotherService anotherService; @PostMapping("login") public WxLoginResponse login() { WxLoginResponse wxLoginResponse = wxService.login("appId", "secret", "code", "clientCredential"); return wxLoginResponse; } } package me.liujiajia.junit5sample.entity; /** * Created by 佳佳 on 2021/4/26. */ public class WxLoginResponse { private String openId; public String getOpenId() { return openId; } public void setOpenId(String openId) { this.openId = openId; } } 4. WxService.java & WxServiceImpl.java package me.liujiajia.junit5sample.service; import me.liujiajia.junit5sample.entity.WxLoginResponse; /** * Created by 佳佳 on 2021/4/26. */ public interface WxService { WxLoginResponse login(String appId, String secret, String code, String clientCredential); } package me.liujiajia.junit5sample.service.impl; import me.liujiajia.junit5sample.entity.WxLoginResponse; import me.liujiajia.junit5sample.service.WxService; import org.springframework.stereotype.Service; /** * Created by 佳佳 on 2021/4/26. */ @Service public class WxServiceImpl implements WxService { @Override public WxLoginResponse login(String appId, String secret, String code, String clientCredential) { WxLoginResponse wxLoginResponse = new WxLoginResponse(); wxLoginResponse.setOpenId("OpenId A"); return wxLoginResponse; } } 5. AnotherService.java & AnotherServiceImpl.java

这个是为了验证之前遇到的服务为空的问题。

package me.liujiajia.junit5sample.service; public interface AnotherService { } package me.liujiajia.junit5sample.service.impl; import me.liujiajia.junit5sample.service.AnotherService; import org.springframework.stereotype.Service; @Service public class AnotherServiceImpl implements AnotherService { } 6. UserControllerTest.java package me.liujiajia.junit5sample; import me.liujiajia.junit5sample.entity.WxLoginResponse; import me.liujiajia.junit5sample.service.WxService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; /** * Created by 佳佳 on 2021/4/26. */ @AutoConfigureMockMvc @SpringBootTest @ExtendWith(MockitoExtension.class) class UserControllerTest { @Autowired private MockMvc mockMvc; @MockBean private WxService wxService; @Test void login() throws Exception { WxLoginResponse wxLoginResponse = new WxLoginResponse(); wxLoginResponse.setOpenId("假用户 OpenId B"); Mockito.when(wxService.login(any(), any(), any(), any())) .thenReturn(wxLoginResponse); ResultActions resultActions = this.mockMvc.perform(post("/user/login")); // 解决中文乱码问题 resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8"); resultActions .andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.content().string(containsString("OpenId B"))) .andDo(MockMvcResultHandlers.print()); } } 附2. 参考文档 Mockito - NullpointerException when stubbing Method Testing MVC Web Controllers with Spring Boot and @WebMvcTest 使用MockMvc与SpringBootTest和使用WebMvcTest之间的区别 Spring Boot Reference Documentation

原文:佳佳的博客



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3